import React from 'react';

export default () => {
  // 1. 类型变量
  {
    function identity<T>(arg: T): T {
      return arg;
    }
    // 使用的第一种方法，包含了参数类型，比较麻烦
    let output = identity<string>("myString");
    // 使用的第二种方法，编译器会自动确定类型，不能确定用上方方法
    let output2 = identity("myString");
  }
  // 2. 使用泛型变量
  {
    function loggingIdentity<T>(arg: T): T {
      // console.log(arg.length);  // Error: T doesn't have .length
      return arg;
    }
    function loggingIdentity2<T>(arg: T[]): T[] {
      console.log(arg.length);  // Array has a .length, so no more error
      return arg;
    }
  }
  // 3. 泛型类型
  {
    function identity<T>(arg: T): T {
      return arg;
    }
    // 跟非泛型函数的类型没什么不同，只是有一个类型参数在最前面
    let myIdentity: <T>(arg: T) => T = identity;
    // 也可以使用不同的泛型参数名
    let myIdentity2: <U>(arg: U) => U = identity;
    // 可以使用带有调用签名的对象字面量
    let myIdentity3: {<T>(arg: T): T} = identity;
    // 使用接口定义
    interface GenericIdentityFn {
      <T>(arg: T): T;
    }
    let myIdentity4: GenericIdentityFn = identity;
  }
  // 4. 泛型接口
  {
    interface GenericIdentityFn<T> {
      (arg: T): T;
    }
    function identity<T>(arg: T): T {
      return arg;
    }
    let myIdentity: GenericIdentityFn<number> = identity;
  }
  // 5. 泛型类
  {
    class GenericNumber<T> {
      zeroValue: T | undefined;
      add: { (x: T, y: T): T } | undefined;
    }

    let myGenericNumber = new GenericNumber<number>();
    myGenericNumber.zeroValue = 0;
    myGenericNumber.add = function(x, y) { return x + y; };

    let stringNumeric = new GenericNumber<string>();
    stringNumeric.zeroValue = "";
    stringNumeric.add = function(x, y) { return x + y; };
  }
  // 6. extends 泛型约束
  {
    interface Lengthwise {
      length: number;
    }

    function loggingIdentity<T extends Lengthwise>(arg: T): T {
      console.log(arg.length);  // 可以使用 length ，因为约束了一个带 length 的接口
      return arg;
    }

    // loggingIdentity(3); // Error, number doesn't have a .length property
    loggingIdentity({length: 10, value: 3});
  }
  // 7. 使用原型属性推断并约束构造函数与类实例的关系
  // 下面例子会报错
  {
    class BeeKeeper {
      hasMask: boolean | undefined;
    }

    class ZooKeeper {
      nametag: string | undefined;
    }

    class Animal {
      numLegs: number | undefined;
    }

    class Bee extends Animal {
      keeper: BeeKeeper | undefined;
    }

    class Lion extends Animal {
      keeper: ZooKeeper | undefined;
    }

    function createInstance<A extends Animal>(c: {new (): A}): A {
      return new c();
    }

    // createInstance(Lion).keeper.nametag;  // typechecks!
    // createInstance(Bee).keeper.hasMask;   // typechecks!
  }
  {
    class Lion {
      keeper: String | undefined;
    }
    function createInstance2<A>(c: {new (): A}): A {
      return new c();
    }
    let lion = createInstance2(Lion);
    lion.keeper = 'xhl';
    alert(lion.keeper);
  }
  return (
    <div>
      <h1>泛型</h1>
      <p>1. 类型变量，可以使返回值的类型与传入参数的类型是相同的。</p>
      <p>2. 由于类型变量没有确定类型，一些特定的属性是不能使用的，比如 T 的情况下不能使用 .length，但是 T[] 的情况就可以使用。</p>
      <p>3. 表示泛型类型只是在非泛型函数加一个类型参数在前面。</p>
      <p>4. 可以使用字面量形式，使用接口定义泛型，可以清楚的直到参数的类型。</p>
      <p>5. 同样可以使用泛型类，类型参数跟在类名后面。</p>
      <p>6. 可以对类型变量使用 extends 关键字实现约束，是的传入的值必须包含 extends 后面约束的类型。</p>
    </div>
  );
}
